package org.flexthinker.binding.bean
{
    import org.flexthinker.binding.bean.support.PropertyReader;
    import org.flexthinker.binding.bean.support.PropertyResolutionError;
    import org.flexthinker.binding.bean.support.PropertyWriter;

    /**
     * Uses a simple dot-separated path syntax to address ActionScript Beans properties
     * of source objects.
     * For example, to create a property representing the <code>firstName</code> property of an object:
     * <p>
     * <pre>
     *      BeanProperty.create("firstName");
     * </pre>
     * </p>
     *
     * Or to create a property representing the <code>firstName</code> property
     * of an object's <code> mother </code> property:
     *
     * <p>
     * <pre>
     *      BeanProperty.create("mother.firstName");
     * </pre>
     * </p>
     *
     * <p>
     *  An instance of <code>BeanProperty</code> is immutable and can be used with different source objects.
     * <br/>
     * When a <code>PropertyChangeListener</code> is added to a <code>BeanProperty</code> for a given source object
     * the <code>BeanProperty</code> starts listening to all objects along the path ( based on that source object )
     * for change notifications, and reflects any changes by notifying the listener associated
     * with the property for that source object.
     * </p>
     *
     * <p>
     * So, in the second example above, if a <code>PropertyChangeListener</code> is added to the property
     * for an object <code>Duke</code>, the <code>PropertyChangeListener</code> is notified when either
     * <code>Duke's</code> mother changes, or <code>Duke's mother's firstName</code> changes.
     * </p>
     *
     * <p>
     * It is very important that any bean property addressed via a <code>BeanProperty</code>
     * follow the Flex specifications, having the  <code>[Bindable]</code> metadata on the property
     * being watched.
     * This also makes it very important that any bean property that could change along the path
     * follow the same rule.
     * </p>
     *
     * @author Dragos Dascalita Haut
     */
    public class BeanProperty extends PropertyHelper
    {
        public function BeanProperty(source:Object, path:String)
        {
            super(source,path);
        }

        public static function create (path:String ):BeanProperty
        {
            return new BeanProperty(null, path );
        }


        override public function getValue():Object
        {
            return PropertyReader.getValue(getSource(), getPath() );
        }


        override public function setValue( value:Object ):void
        {
            PropertyWriter.setValue(getSource(), getPath(), value);
        }


        override public function setSource(value:Object):void
        {
            super.setSource(value);

            try
            {
                watchChangesInPath();
            }
            catch( err:Error)
            {
                throw new PropertyResolutionError(err);
            }

            /* MOVE THIS INTO FormModelProperty
            if ( source is FormModel )
            {
                if ( getPath() && getPath().indexOf(".") >= 0 )
                {
                    throw new Error("Nested properties not allowed with FormModel source object");
                }
                watchChangesInFormModelProperty();
            }
            */
        }

        // --------------------
        //   internal methods
        // ---------------------
        private function watchChangesInPath():void
        {
            resetWatchers();

            var subpaths:Array = getPath().split(".");
            var subpathHost:Object = getSource();
            for each ( var subpath:String in subpaths )
            {
                if ( subpath != null && subpath.length > 0 )
                {
                    listenTo( createValueModel(subpathHost, subpath) );
                }
                subpathHost = subpathHost[subpath];
            }
        }

    }
}